﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using Microsoft.Practices.Unity;
using MiniStock.Infrastructure.Queue;

namespace MiniStock.Infrastructure.Configurator
{
    public class ConfigBasedConfigurator : 
        IEventQueueConfigurator, IPluginConfigurator, IEventProcessorConfigurator, IEntityPersisterConfigurator, IEventDispatcherConfigurator
    {
        #region Housekeeping
        [Dependency]
        public IUnityContainer RootContainer { get; set; }

        private MiniStockConfigurationElement _configSection;

        private MiniStockConfigurationElement ConfigSection
        {
            get
            {
                if(_configSection == null)
                        _configSection =
                    ConfigurationManager.GetSection("miniStock")
                    as MiniStockConfigurationElement;
                return _configSection;
            }
        }

        [InjectionConstructor]
        public ConfigBasedConfigurator()
        {
        }
        
        public ConfigBasedConfigurator(string path)
        {
            _configSection = ConfigurationManager.OpenMappedExeConfiguration(
                new ExeConfigurationFileMap
                    {
                        ExeConfigFilename = path,
                    },
                ConfigurationUserLevel.PerUserRoamingAndLocal
                                 ).GetSection("miniStock") as MiniStockConfigurationElement;
        }
        #endregion

        #region IEventQueueConfigurator
        string IEventQueueConfigurator.GetQueueAddress(string queueName)
        {
            var q = GetQueueByName(queueName, true);
            return q.Address.Replace("${NAME}", queueName);
        }
        #endregion

        #region IPluginConfigurator
        List<string> IPluginConfigurator.GetEntryPoints()
        {
            List<string> result = new List<string>();

            if (ConfigSection.Plugins.Discover)
            {
                foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies().Where(i => !i.IsDynamic))
                    foreach (var type in assembly.GetExportedTypes())
                    {
                        if (type.IsClass &&
                            !type.IsAbstract &&
                            typeof(IProcessorPluginEntryPoint).IsAssignableFrom(type))
                            result.Add(type.AssemblyQualifiedName);
                    }
            }

            for (int i = 0; i < ConfigSection.Plugins.Count; i++)
                result.Add(ConfigSection.Plugins[i].Initializer);

            return result.Distinct().ToList();
        }

        string IPluginConfigurator.GetEntryPoint(string name)
        {
            return GetPluginByName(name, true).Initializer;
        }
        #endregion

        #region IEventProcessorConfigurator
        void IEventProcessorConfigurator.InitEventProcessorDescriptors(List<EventProcessorDescriptor> processors)
        {
            processors.Clear();

            // add event processors
            for (int i = 0; i < ConfigSection.EventProcessors.Count; i++)
            {
                var c = ConfigSection.EventProcessors[i];
                var p = new EventProcessorDescriptor
                {
                    EventProcessorName = c.Name,
                    IsProcessorNode = true,
                    Queue = RootContainer.Resolve<IEventQueue>(),
                    WorkerThreadCount = c.WorkerThreadCount,
                    Plugins = new List<string>(),
                };

                p.Queue.Configure(this, c.InputQueue.Ref);

                for (int j = 0; j < c.Plugins.Count; j++)
                {
                    p.Plugins.Add(GetPluginByName(c.Plugins[j].Ref, true).Initializer);
                }

                processors.Add(p);
            }

            // add event persisters
            {
                var c = ConfigSection.EventPersister;
                var p = new EventProcessorDescriptor()
                {
                    EventProcessorName = c.Name,
                    IsProcessorNode = false,
                    Queue = RootContainer.Resolve<IEventQueue>(),
                    WorkerThreadCount = 1,
                    Plugins = new List<string>(),
                };

                p.Queue.Configure(this, c.InputQueue.Ref);
                processors.Add(p);
            }
        }

        string IEventProcessorConfigurator.GetInputQueueRef(string processorName)
        {
            var q = GetEventProcessorByName(processorName, true);
            return q.InputQueue.Ref;
        }

        string IEventProcessorConfigurator.GetOutputQueueRef(string processorName)
        {
            var q = GetEventProcessorByName(processorName);
            return q.OutputQueue.Ref;
        }

        string IEventProcessorConfigurator.GetName()
        {
            return null;
        }

        List<string> IEventProcessorConfigurator.GetPluginRefs(string processorName)
        {
            List<string> result = new List<string>();
            var q = GetEventProcessorByName(processorName);
            for (int i = 0; i < q.Plugins.Count; i++)
                result.Add(q.Plugins[i].Ref);

            return result;
        }

        int IEventProcessorConfigurator.GetThreadCount(string processorName)
        {
            var processor = GetEventProcessorByName(processorName, true);
            return processor.WorkerThreadCount;
        }
        #endregion

        #region IEntityPersisterConfigurator

        string IEntityPersisterConfigurator.GetInputQueueRef()
        {
            return ConfigSection.EventPersister.InputQueue.Ref;
        }

        string IEntityPersisterConfigurator.GetOutputQueueRef()
        {
            return ConfigSection.EventPersister.OutputQueue.Ref;
        }

        string IEntityPersisterConfigurator.GetName()
        {
            return ConfigSection.EventPersister.Name;
        }

        int IEntityPersisterConfigurator.GetThreadCount()
        {
            return ConfigSection.EventPersister.WorkerThreadCount;
        }

        #endregion

        #region IEventDispatcherConfigurator
        string IEventDispatcherConfigurator.GetName()
        {
            return ConfigSection.EventDispatcher.Name;
        }

        string IEventDispatcherConfigurator.GetQueueRef()
        {
            return ConfigSection.EventDispatcher.InputQueue.Ref;
        }

        int IEventDispatcherConfigurator.GetThreadCount()
        {
            return ConfigSection.EventDispatcher.WorkerThreadCount;
        }
        #endregion

        #region Private members
        private QueueConfigurationElement GetQueueByName(string queueName, bool throwError = false)
        {
            var queues = ConfigSection.Queues;
            QueueConfigurationElement result = null;
            for (int i = 0; i < queues.Count; i++)
            {
                if (queues[i].Name == queueName)
                    return queues[i];
                
                // If not found by name, match by joker character
                else if(queues[i].Name == "*")
                    result = queues[i];
            }

            if (result != null)
                return result;

            if (throwError)
                throw new ApplicationException("Queue was not found with name: " + queueName);

            return null;
        }

        private EventProcessorConfigurationElement GetEventProcessorByName(string processorName, bool throwError = false)
        {
            var processors = ConfigSection.EventProcessors;
            for (int i = 0; i < processors.Count; i++)
            {
                if (processors[i].Name == processorName)
                    return processors[i];
            }

            if (throwError)
                throw new ApplicationException("Event processor was not found with name: " + processorName);

            return null;
        }

        private PluginConfigurationElement GetPluginByName(string pluginName, bool throwError = false)
        {
            var plugins = ConfigSection.Plugins;
            for (int i = 0; i < plugins.Count; i++)
            {
                if (plugins[i].Name == pluginName)
                    return plugins[i];
            }
            if (throwError)
                throw new ApplicationException("Plugin was not found with name: " + pluginName);


            return null;
        }
        #endregion
    }
}
